﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Bankeren.Engine;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using Bankeren.Service;
using System.ServiceModel;
using Bankeren.Engine.Cards;
using System.ServiceModel.Discovery;

namespace Bankeren
{
    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, InstanceContextMode = InstanceContextMode.Single)]
    public class Server : IBankeren
    {
        #region Fields

        private bool isDedicated;
        private Game game;
        private ServiceHost serviceHost;
        private ServiceHost hostHost;
        private BankerenHost host;
        private Dictionary<string, Player> players;
        private string serverName;

        #endregion

        #region Properties

        public bool IsDedicated
        {
            get { return this.isDedicated; }
            set { this.isDedicated = value; }
        }

        private IBankerenCallback CurrentCallback
        {
            get { return OperationContext.Current.GetCallbackChannel<IBankerenCallback>(); }
        }

        private string CurrentSessionID
        {
            get { return OperationContext.Current.SessionId; }
        }

        private Player CurrentPlayer
        {
            get { return this.players[CurrentSessionID]; }
        }

        public string ServerName
        {
            get { return this.serverName; }
            set { this.serverName = value; }
        }

        public IEnumerable<Player> Players
        {
            get { return this.players.Values; }
        }

        #endregion

        #region Ctor

        public Server()
        {
            this.isDedicated = false;
            this.players = new Dictionary<string, Player>();
            this.serverName = "BankerenServer v0.1";
        }

        public Server(bool isDedicated)
        {
            this.isDedicated = isDedicated;
            this.players = new Dictionary<string, Player>();
            this.serverName = "BankerenServer v0.1";
        }

        #endregion

        #region Methods

        #region Public Methods

        #region Game Methods

        public void AddBot(string name, Bot.Difficulty diff)
        {
            game.AddBot(name, diff);
        }

        #endregion

        #region Service Administration

        public void Start()
        {
            this.game = new Game(this);
            this.serviceHost = new ServiceHost(this);
            this.host = new BankerenHost(this);
            this.hostHost = new ServiceHost(host);
            // setup the poker service
            NetTcpBinding netTcp = new NetTcpBinding(SecurityMode.None, true);
            netTcp.ReliableSession.Ordered = true;
            netTcp.ReceiveTimeout = TimeSpan.FromMinutes(10);
            netTcp.SendTimeout = TimeSpan.FromSeconds(15);

            string hostName = System.Net.Dns.GetHostName();
            int port = 1337;
            this.serviceHost.AddServiceEndpoint(typeof(IBankeren), netTcp, string.Format("net.tcp://{0}:{1}/Bankeren", hostName, port));
            // setup the host service
            netTcp = new NetTcpBinding(SecurityMode.None);
            netTcp.SendTimeout = TimeSpan.FromSeconds(15);
            this.hostHost.AddServiceEndpoint(typeof(IBankerenHost), netTcp, string.Format("net.tcp://{0}:{1}/BankerenHost", hostName, port));
            ServiceDiscoveryBehavior discoveryBehavior = new ServiceDiscoveryBehavior();
            this.hostHost.Description.Behaviors.Add(discoveryBehavior);
            this.hostHost.AddServiceEndpoint(new UdpDiscoveryEndpoint());
            discoveryBehavior.AnnouncementEndpoints.Add(new UdpAnnouncementEndpoint());

            this.serviceHost.Open();
            Log("Started");
        }

        public void Stop(string message)
        {
            // Notify players of server stopping
            foreach (Player player in players.Values)
            {
                player.ServerStopped(message);
            }

            // Close service host
            serviceHost.Close();
            Log("Stopped");
        }

        #endregion

        #region Service Interface Methods

        public bool Join(PlayerInfo info)
        {
            lock (players)
            {
                //if (!NameTaken(info.Name))
                //{
                    // Add new player to game
                    Human human = game.AddHuman(CurrentCallback, info);
                    if (human == null)
                        return false;
                    // Store the player
                    players.Add(CurrentSessionID, human);
                    return true;
                //}
                //else
                //    return false;
            }
        }

        public void ChangeInfo(PlayerInfo info)
        {
            lock (players)
            {
                CurrentPlayer.Info = info;
                foreach (Player player in players.Values)
                    player.PlayerInfoChanged(CurrentPlayer.Seat, info);
            }
        }

        public void MakeBid(int bid)
        {
            game.MakeBid(CurrentPlayer.Seat, bid);
        }

        public bool PlayCard(Card card)
        {
            return game.PlayCard(CurrentPlayer, card);
        }

        public void BroadcastChat(string message)
        {
            foreach (Player player in players.Values)
                player.Chat(CurrentPlayer.Seat, message);
        }

        public void Leave()
        {
            throw new NotImplementedException();
        }

        #endregion

        #endregion

        #region Private Methods

        private void Log(string format, params object[] args)
        {
            Console.WriteLine("Server: " + format, args);
        }

        private bool NameTaken(string name)
        {
            foreach (Player player in players.Values)
                if (name == player.Info.Name)
                    return true;
            return false;
        }

        #endregion

        #region Internal Methods

        internal void WriteToConsole(string format, params object[] args)
        {
            if (isDedicated)
                Console.WriteLine(format, args);
        }

        #endregion

        #endregion
    }
}
